package microbes
{
	import away3d.core.base.Object3D;
	import away3d.materials.ColorMaterial;
	import away3d.primitives.Sphere;
	
	import environment.EnvVert;
	import environment.EnvVertVirtual;
	import environment.Environment;
	
	import flash.events.EventDispatcher;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;

	/**
	 *
	 * @author jjung
	 */
	public class Microbe extends EventDispatcher
	{
		//---------------------------------------------------------------------
		//
		//  Static Variables
		//
		//---------------------------------------------------------------------
		public static var MASS_START:Number=0.3;
		public static var MASS_MITOSIS:Number=1.0;
		
		public static var CELLWALL_START:Number=0.5;
		public static var ACCEL_START:Number=3.0;
		public static var TOXIN_START:Number=0.0;
        
        public static var RADIUS_MAX:Number=0.9;
		public static var RADIUS_TO_CHECK:Number=1.1;

        public static var MICROBE_RESOLUTION : Number = 4.0;
        
		//---------------------------------------------------------------------
		//
		//  Variables
		//
		//---------------------------------------------------------------------
		/**
		 * The id of this microbe.
		 */
		public var id:int;

		/**
		 * The non-scaled x coordinate.
		 */
		public var x:Number;

		/**
		 * The non-scaled y coordinate.
		 */
		public var y:Number;

		/**
		 * These are all the properties that can evolve on this microbe.
		 *
		 * Basically evolution change will be a random number between inc and dec and will never
		 * exceed max and never fall below min.
		 *
		 * Every time an evolution occurs, one property will increase and another will decrease,
		 * creating a system of trade-offs.
		 *
		 * Type: Evolver
		 */
		public var evolvers:Array=[];

		/**
		 * The amount of mass that is gained every second in a perfect nutrient solution.
		 */
		public var growthRate:Number=0.4; // 0 - 1.0

		/**
		 * The amount of toxins taken in in one second in a pure toxin solution.
		 */
		public var toxinRate:Number=0.2; // 0 - 1.0

		/**
		 * The amount of nutrients the cell needs to maintain its size and not shrink.
		 */
		public var staticNutrientLoss:Number=0.01;

		/**
		 * The amount of toxins lost per second when the cell is doing nothing.
		 */
		public var staticToxinLoss:Number=0.1;

		/**
		 * If mass goes below this point the cell dies.
		 */
		public var nutrientMassDeath:Number=0.1;

		/**
		 * When the ratio of toxins to nutrients exceeds this threshold, the cell dies.
		 */
		public var toxinRatioDeath:Number=0.8;

		/**
		 * True when undergoing mitosis.
		 */
		public var inMitosis:Boolean=false;

		/**
		 * 0 - 1.0. Resistance to Toxicity: thickness of the cell wall.
		 * Amount of toxic entry to cell is determined by difference between toxicity of env and
		 * cellWallThickness.
		 */
		public var cellWallThickness:uint=0.1;

		/**
		 * The amount of toxins the microbe has sucked out of its env.
		 * Basically every time this hits a certain amount, we subtract some of the green
		 * from the nutrients layer and reset this.
		 */
		public var toxinsAbsorbedCounter:Number=0;

		/**
		 * 0 - 1.0. Acceleration of the cell.
		 * Acceleration determines how fast the cell can move through its env
		 */
		public var accel:Number=5;

		public var speedX:Number=0;
		public var speedY:Number=0;

		public var selected:Boolean=false;
		public var destination:Point;

		//-----------------------------
		//  Misc
		//-----------------------------

		public var env:Environment;

		//-----------------------------
		//  Visual
		//-----------------------------
		public var visible:Boolean=true;
		public var material:ColorMaterial=new ColorMaterial(0x222222, 0.8);
		public var object3D:Sphere;

		//---------------------------------------------------------------------
		//
		//  Properties
		//
		//---------------------------------------------------------------------
		//-----------------------------
		// toxinMass
		//-----------------------------
		/**
		 * @private
		 */
		protected var _toxinMass:Number=0;

		/**
		 * @default 0
		 */
		public function get toxinMass():Number
		{
			return _toxinMass;
		}

		/**
		 * @private
		 */
		public function set toxinMass(value:Number):void
		{
			if (value == _toxinMass)
			{
				return;
			}

			_toxinMass=value;

			if (_toxinMass < 0.0)
			{
				_toxinMass=0.0;
			}

			if (_toxinMass > 1.0)
			{
				_toxinMass=1.0;
			}
		}

		//-----------------------------
		// nutrientMass
		//-----------------------------
		/**
		 * @private
		 */
		protected var _nutrientMass:Number=0;

		/**
		 * @default 0
		 */
		public function get nutrientMass():Number
		{
			return _nutrientMass;
		}

		/**
		 * @private
		 */
		public function set nutrientMass(value:Number):void
		{
			if (value == _nutrientMass)
			{
				return;
			}

			_nutrientMass=value;

			if (_nutrientMass < 0.0)
			{
				_nutrientMass=0.0;
			}

			if (_nutrientMass > 1.0)
			{
				_nutrientMass=1.0;
			}
		}

		//-----------------------------
		// mass
		//-----------------------------
		public function get mass():Number
		{
			return toxinMass + nutrientMass;
		}

		public function get radius():Number
		{
			return ((toxinMass + nutrientMass) / 2.0) * RADIUS_MAX;
		}

		//-----------------------------
		// toxinsToNutrientsRatio
		//-----------------------------
		// The ratio of toxins to nutrients in this cell. 1.0 = all toxins. 0.0 = all nutrients.
		public function get toxinsToNutrientsRatio():Number
		{
			return toxinMass / nutrientMass;
		}

		//-----------------------------
		// diameter
		//-----------------------------
		public function get diameter():Number
		{
			return radius * 2;
		}

		//---------------------------------------------------------------------
		//
		//  Constructor
		//
		//---------------------------------------------------------------------
		/**
		 *  Constructor
		 */
		public function Microbe(env:Environment, id:int=-1)
		{
			this.id=id;
			this.env=env;

			object3D=new Sphere(material, 10, MICROBE_RESOLUTION, MICROBE_RESOLUTION);

			nutrientMass=MASS_START;

			toxinMass=0.0;

			cellWallThickness=CELLWALL_START;
		}

		//---------------------------------------------------------------------
		//
		//  Methods
		//
		//---------------------------------------------------------------------
		public function resetPhysics():void
		{
			speedX=0;
			speedY=0;
		}

        public function checkBounds() : void
        {
            // Check bounds
            if (x < Environment.BORDER)
                x=Environment.BORDER;
            if (x > env.width - Environment.BORDER - 1)
                x=env.width - Environment.BORDER -1;
            if (y < Environment.BORDER)
                y=Environment.BORDER;
            if (y > env.height - Environment.BORDER - 1)
                y=env.height - Environment.BORDER - 1;
        }
		public function update(timeElapsed:Number, env:Environment):void
		{
            checkBounds();
            
			var accelX:Number=0;
			var accelY:Number=0;

			if (inMitosis)
			{
				return;
			}

			var angle:Number=0;

			var p:Point;
			var iMaxNutrients:int=-1;
			var maxNutrients:Number=0;

			var pointsToCheck:Array=[];

			var numPointsToCheck:int=8;

			var i;

			for (i=0; i < numPointsToCheck; i++)
			{
				p=new Point();

				p.x = x + Math.cos(angle) * RADIUS_TO_CHECK;
				p.y = y + Math.sin(angle) * RADIUS_TO_CHECK;

				pointsToCheck.push(p);

				angle+=(Math.PI * 2) / numPointsToCheck;
			}

			var avgTox:Number=0;
			var avgNut:Number=0;
			var avgFlu:Number=0.5;

			for (i=0; i < numPointsToCheck; i++)
			{
				var vv:EnvVertVirtual=env.getVirtualVertValue(pointsToCheck[i].x, pointsToCheck[i].y);

				avgTox+=vv.colorValues[EnvVert.TOXIC];
				var nut:Number=vv.colorValues[EnvVert.NUTRIENT];
				avgNut+=nut;
				avgFlu+=vv.colorValues[EnvVert.FLUID];

				if (nut > maxNutrients)
				{
					maxNutrients=nut;
					iMaxNutrients=i;
				}
			}

			if (!destination)
			{
				// Let's calculate movement based on the direction that leads to the most food.
				if (iMaxNutrients != -1)
				{
					accelX+=Math.cos(((Math.PI * 2) / numPointsToCheck) * iMaxNutrients) * accel;
					accelY+=Math.sin(((Math.PI * 2) / numPointsToCheck) * iMaxNutrients) * accel;
				}
			}
			else
			{
				var xDir:Number=destination.x - object3D.x;
				var yDir:Number=destination.y - object3D.y;

				var len:Number=Math.sqrt(xDir * xDir + yDir * yDir);
				xDir/=len;
				yDir/=len;

				accelX+=xDir * accel;
				accelY+=yDir * accel;
			}

			speedX+=accelX * timeElapsed;
			speedY+=accelY * timeElapsed;

			x+=speedX * timeElapsed;
			y+=speedY * timeElapsed;

			if (destination)
			{
				if (Math.abs(object3D.x - destination.x) < 5 && Math.abs(object3D.y - destination.y) < 5)
				{
					destination=null;
				}
			}

			avgTox/=numPointsToCheck;
			avgNut/=numPointsToCheck;
			avgFlu/=numPointsToCheck;

			// Friction
			speedX-=speedX * avgFlu;
			speedY-=speedY * avgFlu;

			var growthIntake:Number=growthRate * timeElapsed * avgNut;

			// We want to subtract the growth intake from the surrounding environment.
            var verts : Array = env.getFourNearestVerts(x, y);
            
            for (var v : int = 0; v < 4; v++)
            {
    			var envVert:EnvVert=verts[v];
    
                envVert.addVertColor(EnvVert.NUTRIENT, -growthIntake / 4);
                envVert.addVertValue(EnvVert.NUTRIENT, growthIntake / 4);
            }

			var nutrientLoss:Number=-staticNutrientLoss * timeElapsed;

			nutrientLoss-=(Math.abs(accelX) / accel) * timeElapsed * 0.05;
			nutrientLoss-=(Math.abs(accelY) / accel) * timeElapsed * 0.05;

			var nutrientMassChange:Number=growthIntake + nutrientLoss;

			nutrientMass+=nutrientMassChange;

			var toxinIntake:Number=toxinRate * timeElapsed * avgTox;
			var toxinLoss:Number=staticToxinLoss * timeElapsed;

			toxinMass+=(toxinIntake * (1.0 - cellWallThickness)) - toxinLoss;

			env.subtractToxins(object3D.x, object3D.y, radius * 3, toxinIntake * (1.0 - cellWallThickness));

			toxinsAbsorbedCounter=0;

			if (toxinsToNutrientsRatio >= toxinRatioDeath)
			{
				//env.microbesToSplice.push(this);
			}

			if (nutrientMass >= 0.99)
			{
				//new Mitosis(this, splitSelf());
			}
			else if (nutrientMass < nutrientMassDeath)
			{
				//MicrobialLife.getInstance().microbesToSplice.push(this);
			}

            checkBounds();

			// Update the visual object.
			object3D.x=x * Environment.SIZE_MULTIPLIER;
			object3D.y=y * Environment.SIZE_MULTIPLIER;
            object3D.z = 0;
			object3D.radius=radius * Environment.SIZE_MULTIPLIER;

			material.color=fillColor;
		}

		public function splitSelf():Microbe
		{
			//var newMicrobe : Microbe = new Microbe();
			/*newMicrobe.x = x;
			newMicrobe.y = y;
			newMicrobe.toxinMass = toxinMass;
			newMicrobe.nutrientMass = nutrientMass;
			newMicrobe.accel = accel;
			newMicrobe.cellWallThickness = cellWallThickness;
			newMicrobe.destination = destination;
			newMicrobe.growthRate = growthRate;
			newMicrobe.nutrientMassDeath = nutrientMassDeath;
			newMicrobe.staticNutrientLoss = staticNutrientLoss;
			newMicrobe.staticToxinLoss = staticToxinLoss;
			newMicrobe.toxinRate = toxinRate;*/

			//return newMicrobe;

			return null;
		}

		public function get fillColor():uint
		{
			var fillColor:uint=0x0;

			// Red = cellWallThickness.
			fillColor|=uint(toxinsToNutrientsRatio * 255) << 16;

			// Green = energy.
			fillColor|=uint((1.0 - toxinsToNutrientsRatio) * 255) << 8;

			// Blue = accel.
			fillColor|=accel;

			return fillColor;
		}
	}
}
